using Engine.Graphics;
using System.Drawing;
using System.IO;

namespace street
{
    public struct Properties
    {
        #region Private Fields

        private const byte Version = 0x03;
        private Color colorStart, colorEnd, colorRight, colorLeft;
        private int inclinationEnd;
        private int inclinationMiddle;
        private int inclinationStart;
        private Material materialForRail;
        private Material materialForRoad;
        private int railLeftEnd;
        private int railLeftMiddle;
        private int railLeftStart;
        private int raiRightEnd;
        private int raiRightMiddle;
        private int raiRightStart;
        private float streetwidthEnd;
        private float streetwidthStart;

        #endregion Private Fields

        #region Public Constructors

        public Properties(Properties properties)
        {
            this.streetwidthStart = properties.streetwidthStart;
            this.streetwidthEnd = properties.streetwidthEnd;
            this.inclinationStart = properties.inclinationStart;
            this.inclinationEnd = properties.inclinationEnd;
            this.inclinationMiddle = properties.inclinationMiddle;
            this.railLeftMiddle = properties.railLeftMiddle;
            this.railLeftStart = properties.railLeftStart;
            this.railLeftEnd = properties.railLeftEnd;
            this.raiRightStart = properties.raiRightStart;
            this.raiRightMiddle = properties.raiRightMiddle;
            this.raiRightEnd = properties.raiRightEnd;

            this.colorStart = properties.colorStart;
            this.colorEnd = properties.colorEnd;
            this.colorLeft = properties.colorLeft;
            this.colorRight = properties.colorRight;

            this.materialForRoad = new Material(properties.materialForRoad);
            this.materialForRail = new Material(properties.materialForRail);
        }

        public Properties(float streetwidth)
        {
            this.streetwidthStart = streetwidth;
            this.streetwidthEnd = streetwidth;
            this.inclinationStart = 0;
            this.inclinationMiddle = 0;
            this.inclinationEnd = 0;
            this.railLeftStart = 0;
            this.railLeftMiddle = 0;
            this.railLeftEnd = 0;
            this.raiRightStart = 0;
            this.raiRightMiddle = 0;
            this.raiRightEnd = 0;
            this.colorStart = Color.Black;
            this.colorEnd = Color.Black;
            this.colorLeft = Color.Black;
            this.colorRight = Color.Black;
            this.materialForRail = new Material("empty", "empty", "empty", EnumMappingMode.material144);
            this.materialForRoad = new Material("empty", "empty", "empty", EnumMappingMode.material144);
        }

        #endregion Public Constructors

        #region Public Properties

        public Color ColorEnd
        {
            get { return colorEnd; }
            set { colorEnd = value; }
        }

        public Color ColorLeft
        {
            get { return colorLeft; }
            set { colorLeft = value; }
        }

        public Color ColorRight
        {
            get { return colorRight; }
            set { colorRight = value; }
        }

        public Color ColorStart
        {
            get { return colorStart; }
            set { colorStart = value; }
        }

        public int InclinationEnd
        {
            get { return inclinationEnd; }
            set { street.streetTool.hasPropertiesChanged = true; inclinationEnd = value; }
        }

        public int InclinationMiddle
        {
            get { return inclinationMiddle; }
            set { inclinationMiddle = value; }
        }

        public int InclinationStart
        {
            get { return inclinationStart; }
            set { street.streetTool.hasPropertiesChanged = true; inclinationStart = value; }
        }

        public Material MaterialForRail
        {
            get { return materialForRail; }
            set { materialForRail = new Material(value); }
        }

        public Material MaterialForRoad
        {
            get { return materialForRoad; }
            set { materialForRoad = new Material(value); }
        }

        public int RailLeftEnd
        {
            get { return railLeftEnd; }
            set { street.streetTool.hasPropertiesChanged = true; railLeftEnd = value; }
        }

        public int RailLeftMiddle
        {
            get { return railLeftMiddle; }
            set { street.streetTool.hasPropertiesChanged = true; railLeftMiddle = value; }
        }

        public int RailLeftStart
        {
            get { return railLeftStart; }
            set { street.streetTool.hasPropertiesChanged = true; railLeftStart = value; }
        }

        public int RaiRightEnd
        {
            get { return raiRightEnd; }
            set { street.streetTool.hasPropertiesChanged = true; raiRightEnd = value; }
        }

        public int RaiRightMiddle
        {
            get { return raiRightMiddle; }
            set { street.streetTool.hasPropertiesChanged = true; raiRightMiddle = value; }
        }

        public int RaiRightStart
        {
            get { return raiRightStart; }
            set { street.streetTool.hasPropertiesChanged = true; raiRightStart = value; }
        }

        public float StreetwidthEnd
        {
            get { return streetwidthEnd; }
            set { street.streetTool.hasPropertiesChanged = true; streetwidthEnd = value; }
        }

        public float StreetwidthStart
        {
            get { return streetwidthStart; }
            set { street.streetTool.hasPropertiesChanged = true; streetwidthStart = value; }
        }

        #endregion Public Properties

        #region Internal Methods

        internal static Properties FromStream(BinaryReader br)
        {
            Properties p = new Properties();

            switch (br.ReadByte())
            {
                case 0x01:
                    p = LoadV1Properties(br);
                    break;

                case 0x02:
                    p = LoadV2Properties(br);
                    break;

                case 0x03:
                    p = LoadV3Properties(br);
                    break;

                default:
                    br.BaseStream.Position -= 1;
                    p = LoadV0Properties(br);
                    break;
            }
            return p;
        }

        internal void ToStream(BinaryWriter writer)
        {
            writer.Write(Version);
            writer.Write(streetwidthStart);
            writer.Write(streetwidthEnd);
            writer.Write(inclinationStart);
            writer.Write(inclinationMiddle);
            writer.Write(inclinationEnd);

            writer.Write(railLeftStart);
            writer.Write(railLeftMiddle);
            writer.Write(railLeftEnd);

            writer.Write(raiRightStart);
            writer.Write(raiRightMiddle);
            writer.Write(raiRightEnd);

            writer.Write(ColorStart.ToArgb());
            writer.Write(ColorEnd.ToArgb());
            writer.Write(ColorLeft.ToArgb());
            writer.Write(ColorRight.ToArgb());

            materialForRoad.SaveToStream(writer);
            materialForRail.SaveToStream(writer);
        }

        #endregion Internal Methods

        #region Private Methods

        private static Properties LoadV0Properties(BinaryReader br)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();

            p.ColorStart = Color.Black;
            p.ColorEnd = Color.Black;
            p.ColorLeft = Color.Black;
            p.ColorRight = Color.Black;

            p.materialForRoad = Material.FromStream(br);
            p.materialForRail = Material.FromStream(br);
            return p;
        }

        private static Properties LoadV1Properties(BinaryReader br)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            p.ColorStart = Color.Black;
            p.ColorEnd = Color.Black;
            p.ColorLeft = Color.Black;
            p.ColorRight = Color.Black;

            p.materialForRoad = Material.FromStream(br);
            p.materialForRail = Material.FromStream(br);
            return p;
        }

        private static Properties LoadV2Properties(BinaryReader br)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            p.ColorStart = Color.FromArgb(br.ReadInt32());
            p.ColorEnd = Color.FromArgb(br.ReadInt32());
            p.ColorLeft = Color.FromArgb(br.ReadInt32());
            p.ColorRight = Color.FromArgb(br.ReadInt32());

            p.materialForRoad = Material.FromStream(br);
            p.materialForRail = Material.FromStream(br);

            // Calculate inclination at the middle of the road.
            p.InclinationMiddle = (p.inclinationStart + p.InclinationEnd) / 2;
            return p;
        }

        private static Properties LoadV3Properties(BinaryReader br)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationMiddle = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            p.ColorStart = Color.FromArgb(br.ReadInt32());
            p.ColorEnd = Color.FromArgb(br.ReadInt32());
            p.ColorLeft = Color.FromArgb(br.ReadInt32());
            p.ColorRight = Color.FromArgb(br.ReadInt32());

            p.materialForRoad = Material.FromStream(br);
            p.materialForRail = Material.FromStream(br);
            return p;
        }

        #endregion Private Methods
    }
}